package com.lagou.edu.factory;

import com.lagou.edu.annotation.Autowired;
import com.lagou.edu.annotation.ComponentScan;
import com.lagou.edu.annotation.Service;
import com.lagou.edu.annotation.Transactional;
import com.lagou.edu.utils.ClassUtil;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @program: lagou-transfer
 * @author: GYL
 * @create: 2020-09-09 23:06
 * @description: 注解工厂
 * 1.根据配置类 SpringConfig 获取其扫描注解
 * 2.拿到扫描的路径 （路径为空 默认取config包下的路径） 获取路径下所有的 class
 * 3.反射判断类上是否有 @service 注解
 * 4.有@service 注解 则继续判断 是否有@Transactional 事物注解 有则 记录id到transSet代理对象集合 为后面创建代理对象做准备
 * 5.接着判断对象属性上是否有Autowired自动注入注解 如果有也需要 先进行 存储autoMap 为后面 注入对象作准备
 * 6.整个判断结束后 把 有 @service 注解放入 容器里 同时将ID  存入beanNames集合（beanNames方便后续操作）
 * 7.操作需要自动注入的对象 分两种情况 根据name去找 没找到 就根据 类型去找
 * 8.操作需要针对事物创建代理对象重新存入容器 判断是否实现接口 决定用哪种动态代理 创建代理对象
 */
public class AnnotationConfigApplicationContext extends ApplicationContext {
    /**
     * 存储beanName(id)
     */
    private Set<String> beanNames = new HashSet<>();
    /**
     * 存储记录需要自动注入的map
     */
    private Map<String, Object> autoMap = new HashMap<>(16);
    /**
     * 存储记录需要代理对象的集合
     */
    private Set<String> transSet = new HashSet<>();

    public AnnotationConfigApplicationContext(Class componentClasses) {
        System.out.println("启用注解模式");
        pare(componentClasses);
    }


    /**
     * 解析配置类
     *
     * @param componentClasses
     */
    private void pare(Class componentClasses) {
        String basePackage = "";
        //获取扫描注解
        ComponentScan componentScan = (ComponentScan) componentClasses.getAnnotation(ComponentScan.class);
        if (componentScan != null) {
            //不为空获取扫描信息
            basePackage = componentScan.basePackage();
        }
        if (StringUtils.isBlank(basePackage)) {
            //没有扫描注解 或者 没有配置扫描信息 暂时走自己类所在的包当作扫描包
            basePackage = componentClasses.getPackage().getName();
        }
        //获取所有类信息
        Set<Class<?>> classSet = ClassUtil.getClassSet(basePackage);
        //遍历 class集合 判断是否有@service注解
        try {

            //处理需要放入容器的对象 同时记录需要操作自动注入的ID 和 创建事物代理 对象的ID
            disposeInstanceBean(classSet);
            //处理需要注入对象的内容
            disposeAutowired();
            //处理需要事物创建代理对象重新装入集合方法
            disposeTransactional();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 处理需要放入容器的对象 同时记录需要操作自动注入的ID 和 创建事物代理 对象的ID
     * @param classSet
     * @throws Exception
     */
    private void disposeInstanceBean(Set<Class<?>> classSet) throws Exception {
        //把所有 @service 存入 map
        for (Class clazz : classSet) {
            Service service = (Service) clazz.getAnnotation(Service.class);
            //没有@Service注解的直接跳出当前循环表示不需要容器管理
            if (service == null) {
                continue;
            }
            //获取id
            String id = getId(clazz, service.value());
            //存在事物 记录下事物的id
            if (hasTransactional(clazz)) {
                transSet.add(id);
            }
            //把存在 要注入 的信息 存储 起来
            hasFieldAutowired(clazz, id);
            //创建对象实例
            Object o = clazz.newInstance();
            //存入 map
            put(id, o);
            beanNames.add(id);
        }
    }

    /**
     * 处理需要注入对象的内容
     * 遍历需要注入的map id集合
     * 判断是否存在容器中
     * 拿到事先存储好的 对应 属性Field 开始自动装配流程
     * byName
     * ByType
     * 找到后 反射开始进行暴力 赋值
     * f.setAccessible(true);
     *（TODO 这里老师写的set方法应该不用了）
     * 重新将对象放回容器
     * @throws Exception
     */
    private void disposeAutowired() throws Exception {
        //遍历 要注入的信息
        Set<String> ids = autoMap.keySet();
        for (String id : ids) {
            //容器 是否存在要注入的ID
            if (containsKey(id)) {
                //拿到 要注入的 Field 对象
                Field field = (Field) autoMap.get(id);
                //拿到 属性的 名称
                String name = field.getName();
                //根据 名称 自动装配
                Object auto = autoWiredByName(name);
                if (auto == null) {
                    //根据 type 自动装配
                    auto = autoWiredByType(field.getType());
                }
                if (auto == null) {
                    throw new Exception("没有找到要注入的类，请检查");
                }
                //获取要被注入的对象id
                Object object = get(id);
                // 获取obj类的字节文件对象
                Class c = object.getClass();
                // 获取该类的成员变量
                Field f = c.getDeclaredField(name);
                // 取消语言访问检查
                f.setAccessible(true);
                // 给变量赋值
                f.set(object, auto);
                //重新设置值 给 map
                put(id, object);
            }
        }

    }

    /**
     * 根据类型自动注入
     *
     * @param aClass
     * @return
     */
    private Object autoWiredByType(Class<?> aClass) throws ClassNotFoundException {
        for (String id : beanNames) {
            //获取 bean对象
            Object object = get(id);
            //判断属性对象是否是 其子类
            if (aClass.isAssignableFrom(object.getClass())) {
                return object;
            }
        }
        return null;
    }

    /**
     * 根据名字 自动装配对象
     *
     * @param name
     * @return
     */
    private Object autoWiredByName(String name) {
        if (containsKey(name)) {
            return get(name);
        }
        return null;
    }

    /**
     * 把存在 要注入 的信息 存储 起来
     *
     * @param clazz
     * @param id
     */
    private void hasFieldAutowired(Class clazz, String id) {
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            Autowired annotation = field.getAnnotation(Autowired.class);
            if (annotation == null) {
                //没有注入的值 不做操作   
                continue;
            }
            //存储 要注入的ID 为 key  要被注入的对象的field.getName() 为value
            autoMap.put(id, field);
        }
    }

    /**
     * 判断是否有事物注解
     *
     * @param clazz
     * @return
     */
    private Boolean hasTransactional(Class clazz) {
        Transactional transactional = (Transactional) clazz.getAnnotation(Transactional.class);
        //存在事物 返回true
        if (transactional != null) {
            return true;
        }
        //不存在继续遍历方法
        Method[] methods = clazz.getMethods();
        for (Method method:methods){
            transactional= method.getAnnotation(Transactional.class);
            if(transactional!=null){
                //存在事物直接 返回true
                return true;
            }
        }
        return false;

    }

    /**
     * 处理需要事物创建代理对象重新装入集合方法
     * 操作事物id集合
     * 去除容器内原先的对象
     * 判断是否是实现接口 实现接口 用jdk动态代理 不实现接口用cglib动态代理
     * 创建代理对象 再存入容器
     */
    private void disposeTransactional() throws Exception {
        //设置事物对象
        for (String id : transSet) {
            Object object = get(id);
            //拿到代理工厂
            ProxyFactory proxyFactory = (ProxyFactory) get("proxyFactory");
            //判断是否是接口
            Class<?>[] interfaces = object.getClass().getInterfaces();
            if (interfaces.length>0) {
                System.out.println("使用jdk动态代理");
                Object jdkProxy = proxyFactory.getJdkProxy(object);
                put(id, jdkProxy);
                continue;
            }
            System.out.println("使用cglib动态代理");
            Object cglibProxy = proxyFactory.getCglibProxy(object);
            put(id,cglibProxy);
        }
    }

    /**
     * 获取 bean id
     *
     * @param id
     * @return
     */
    private String getId(Class clazz, String id) {
        if (StringUtils.isBlank(id)) {
            //如果没有设置 value  或者 设置"" 直接取类名首字母小写为id
            String className = clazz.getSimpleName();// 获取类名
            String small = className.substring(0, 1).toLowerCase();// 获取首字母将首字母变为小写
            return small + className.substring(1);// 获得已小写字母开头的类名
        }
        return id;
    }
}